/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// r_main.c

#include "quakedef.h"

#define CUBEMAP_SIZE	64

QTEXTURE d3d_WhiteTexture;
QTEXTURE d3d_GreyTexture;
QTEXTURE d3d_BlackTexture;
QTEXTURE d3d_NoTextureMip;
QTEXTURE PaletteRowTextures[16];


__declspec (align (16))
struct mainconstants_t
{
	float WorldMatrix[16];
	float forwardVec[3];
	float VidWidth;
	float upVec[3];
	float VidHeight;
	float rightVec[3];
	float particleSize;
	float WarpTime;
	float WarpScale;
	float WarpSpeed;
	float Gravity;
	float WarpFactor;
	float SkySpeedScale[2];
	float SkyFog;
	float FogRGB[3];
	float FogDensity;
	float EyePositionWorld[3];
	float TestFloat;
	float InverseViewProj[16];
	float ProjectionMatrix[16];
	float ViewMatrix[16];
};

__declspec (align (16))
struct objectconstants_t
{
	float EntityMatrix[16];
	float LocalMatrix[16];
	float lerpBlend;
	float ShadeLight[3];
	float ShadeVector[3];
	float Alpha;
};

ID3D10Buffer *d3d_MainConstants = NULL;
ID3D10Buffer *d3d_ObjectConstants = NULL;

class CMainHandler : public CD3DHandler
{
public:
	void OnCreateDevice (void)
	{
		// create our constant buffer
		D3D10_BUFFER_DESC cbDesc1 = {
			sizeof (mainconstants_t),
			D3D10_USAGE_DYNAMIC,
			D3D10_BIND_CONSTANT_BUFFER,
			D3D10_CPU_ACCESS_WRITE,
			0
		};

		d3d_Device->CreateBuffer (&cbDesc1, NULL, &d3d_MainConstants);

		// create our constant buffer
		D3D10_BUFFER_DESC cbDesc2 = {
			sizeof (objectconstants_t),
			D3D10_USAGE_DYNAMIC,
			D3D10_BIND_CONSTANT_BUFFER,
			D3D10_CPU_ACCESS_WRITE,
			0
		};

		d3d_Device->CreateBuffer (&cbDesc2, NULL, &d3d_ObjectConstants);

		// create generic textures
		unsigned white = 0xffffffff;
		unsigned black = 0;
		unsigned grey = 0x80808080;
		unsigned nomip[4] = {0x00ff00ff, 0xff00ff00, 0x00ff00ff, 0xff00ff00};

		d3d_WhiteTexture.CreateTexture ((byte *) &white, 1, 1, TEX_32BIT);
		d3d_BlackTexture.CreateTexture ((byte *) &black, 1, 1, TEX_32BIT);
		d3d_GreyTexture.CreateTexture ((byte *) &grey, 1, 1, TEX_32BIT);
		d3d_NoTextureMip.CreateTexture ((byte *) nomip, 2, 2, TEX_32BIT);

		// PaletteRowTextures are deferred to SCR_InitPics because the palette hasn't yet been loaded here
	}

	void OnReleaseDevice (void)
	{
		SAFE_RELEASE (d3d_MainConstants);
		SAFE_RELEASE (d3d_ObjectConstants);

		d3d_WhiteTexture.Release ();
		d3d_GreyTexture.Release ();
		d3d_BlackTexture.Release ();
		d3d_NoTextureMip.Release ();
		/*
		for (int i = 0; i < 16; i++)
			SAFE_RELEASE (PaletteRowTextures[i]);
		*/
	}

	void OnModeChange (void)
	{
		// this cbuffer always goes in slot 1
		d3d_Device->VSSetConstantBuffers (1, 1, &d3d_MainConstants);
		d3d_Device->GSSetConstantBuffers (1, 1, &d3d_MainConstants);
		d3d_Device->PSSetConstantBuffers (1, 1, &d3d_MainConstants);

		// this cbuffer always goes in slot 2
		d3d_Device->VSSetConstantBuffers (2, 1, &d3d_ObjectConstants);
		d3d_Device->PSSetConstantBuffers (2, 1, &d3d_ObjectConstants);
		d3d_Device->GSSetConstantBuffers (2, 1, &d3d_ObjectConstants);
	}
} d3d_MainHandler;


// copy off the visedicts so that we can safely add statics at any time
entity_t *r_visedicts[MAX_EDICTS];
int r_numvisedicts;

entity_t	r_worldentity;

int			c_brush_polys, c_alias_polys;
int			c_draw_call;

playertexture_t PlayerTextures[MAX_SCOREBOARD];		// up to 16 color translated skins

// screen size info
refdef_t	ref;

texture_t	r_notexture_mip;

cvar_t *r_norefresh;
cvar_t *r_drawentities;
cvar_t *r_drawviewmodel;
cvar_t *r_speeds;
cvar_t *r_fullbright;
cvar_t *r_lightmap;
cvar_t *r_lightscale;
cvar_t *r_wateralpha;
cvar_t *r_dynamic;
cvar_t *r_novis;
cvar_t *r_draworder;
cvar_t *r_lockpvs;
cvar_t *r_lockfrustum;
cvar_t *r_clearcolor;
cvar_t *gl_finish;
cvar_t *gl_clear;
cvar_t *gl_cull;
cvar_t *gl_polyblend;
cvar_t *gl_nocolors;
cvar_t *gl_doubleeyes;
cvar_t *gl_texture_anisotropy;
cvar_t *r_batchmode;
cvar_t *r_showbboxes;
cvar_t *r_particle_size;
cvar_t *r_realtime_lighting;


void R_VisReset (void)
{
	// causes vis to be recalculated
	ref.ViewLeaf[1] = NULL;
	ref.ViewLeaf[2] = NULL;
}


BOOL R_CullSphere (float *center, float radius, int clipflags)
{
	// hack
	float mins[3] = {center[0] - radius, center[1] - radius, center[2] - radius};
	float maxs[3] = {center[0] + radius, center[1] + radius, center[2] + radius};

	return R_CullBox (ref.Frustum, mins, maxs, clipflags);
}


/*
=================
R_CullBox

Returns TRUE if the box is completely outside the frustom
=================
*/
BOOL R_CullBox (mplane_t *frustum, float *mins, float *maxs, int clipflags)
{
	int i;
	mplane_t *p;

	for (i = 0, p = frustum; i < 4; i++, p++)
	{
		// don't need to clip against it
		if (!(clipflags & (1 << i))) continue;

		switch (p->signbits)
		{
		default:
		case 0: if (p->normal[0] * maxs[0] + p->normal[1] * maxs[1] + p->normal[2] * maxs[2] < p->dist) return TRUE; break;
		case 1: if (p->normal[0] * mins[0] + p->normal[1] * maxs[1] + p->normal[2] * maxs[2] < p->dist) return TRUE; break;
		case 2: if (p->normal[0] * maxs[0] + p->normal[1] * mins[1] + p->normal[2] * maxs[2] < p->dist) return TRUE; break;
		case 3: if (p->normal[0] * mins[0] + p->normal[1] * mins[1] + p->normal[2] * maxs[2] < p->dist) return TRUE; break;
		case 4: if (p->normal[0] * maxs[0] + p->normal[1] * maxs[1] + p->normal[2] * mins[2] < p->dist) return TRUE; break;
		case 5: if (p->normal[0] * mins[0] + p->normal[1] * maxs[1] + p->normal[2] * mins[2] < p->dist) return TRUE; break;
		case 6: if (p->normal[0] * maxs[0] + p->normal[1] * mins[1] + p->normal[2] * mins[2] < p->dist) return TRUE; break;
		case 7: if (p->normal[0] * mins[0] + p->normal[1] * mins[1] + p->normal[2] * mins[2] < p->dist) return TRUE; break;
		}
	}

	return FALSE;
}


int R_EntitySortFunc (entity_t **e1ptr, entity_t **e2ptr)
{
	entity_t *e1 = *e1ptr;
	entity_t *e2 = *e2ptr;

	if (e1->model->type == e2->model->type)
	{
		if (e1->model->type == mod_alias)
		{
			// sort poses for keeping vertex buffer changes down
			if (e1->model == e2->model)
			{
				if (e1->currpose == e2->currpose)
					return e1->prevpose - e2->prevpose;
				else return e1->currpose - e2->currpose;
			}
			else return e1->model - e2->model;
		}
		else return e1->model - e2->model;
	}
	else return ((int) e1->model->type - (int) e2->model->type);
}


/*
=============
R_DrawEntitiesOnList
=============
*/
void R_DrawEntitiesOnList (void)
{
	// if there's 2 entities we still don't need to sort as they're either both different or both the same
	if (r_numvisedicts > 2) qsort (r_visedicts, r_numvisedicts, sizeof (entity_t *), (sortfunc_t) R_EntitySortFunc);

	// draw sprites seperately, because of alpha blending
	for (int i = 0; i < r_numvisedicts; i++)
	{
		entity_t *e = r_visedicts[i];

		// the entity is on the list but has been culled; we don't want to draw the entity but we do want to
		// draw it's shadow
		if (e->culled) continue;

		switch (e->model->type)
		{
		case mod_alias: R_DrawAliasModel (e); break;
		case mod_brush: R_DrawBrushModel (e); break;
		default: break;
		}
	}
}


/*
=============
R_DrawViewModel
=============
*/
void R_DrawViewModel (void)
{
	if (!r_drawviewmodel->boolean) return;
	if (cl.stats[STAT_HEALTH] <= 0) return;
	if (!cl.viewent.model) return;

	// set up effects
	cl.viewent.effects |= EF_DEPTHHACK;

	// and set it up
	R_SetupEntity (&cl.viewent);
}


int SignbitsForPlane (mplane_t *out)
{
	// for fast box on planeside test
	int j, bits = 0;

	for (j = 0; j < 3; j++)
	{
		if (out->normal[j] < 0)
			bits |= 1 << j;
	}

	return bits;
}


/*
===============
R_SetupFrame
===============
*/
void R_SetupFrame (void)
{
	// don't allow cheats in multiplayer
	if (cl.maxclients > 1)
	{
		Cvar_SetValue (r_fullbright, 0);
		Cvar_SetValue (r_lightmap, 0);
	}

	R_AnimateLight (cl.time);

	ref.FrameCount++;

	ref.ViewLeaf[0] = Mod_PointInLeaf (ref.ViewOrigin, cl.worldmodel);

	V_CalcBlend ();

	c_brush_polys = 0;
	c_alias_polys = 0;
	c_draw_call = 0;
}


/*
=============
R_SetupGL
=============
*/
void R_SetupViewpoint (void)
{
	// creating 2 viewports; one for regular drawing, the second for our shadow map
	D3D10_VIEWPORT vp = {0, 0, vid.ViewWidth, vid.ViewHeight, 0, 1};

	// set the correct viewport
	d3d_Device->RSSetViewports (1, &vp);

	// the projection matrix may be only updated when the refdef changes
	if (scr.DrawMapshot)
	{
		ref.ProjectionMatrix.Identity ();
		ref.ProjectionMatrix.Perspective (68.038696f, 68.038696f, 4.0f, 14188.960f);
	}
	else
	{
		// 14188.960 - max extent of -/+ 4096 in each direction
		ref.ProjectionMatrix.Identity ();
		ref.ProjectionMatrix.Perspective (ref.FovX, ref.FovY, 4.0f, 14188.960f);
	}

	// view is updated every frame
	ref.ViewMatrix.Identity ();
	ref.ViewMatrix.Camera (ref.ViewOrigin, ref.ViewAngles);
}


void R_SetupGL (void)
{
	// compute the global MVP (world is left at identity for now - which is the same as not multiplying by it)
	ref.MVPMatrix.Mult (ref.ViewMatrix, ref.ProjectionMatrix);

	// and extract the frustum from it
	if (!r_lockfrustum->boolean)
		ref.MVPMatrix.ToFrustum (ref.Frustum, ref.ViewOrigin);

	// and fill in our shared shader constants
	mainconstants_t *c = NULL;

	if (SUCCEEDED (d3d_MainConstants->Map (D3D10_MAP_WRITE_DISCARD, 0, (void **) &c)))
	{
		ref.MVPMatrix.Get (c->WorldMatrix);
		Vector3Copy (c->forwardVec, ref.ViewMatrix.Forward ());
		Vector3Copy (c->upVec, ref.ViewMatrix.Up ());
		Vector3Copy (c->rightVec, ref.ViewMatrix.Right ());
		c->particleSize = r_particle_size->value * 0.02f;
		c->WarpTime = cl.time - ((M_PI * 2) * (int) (cl.time / (M_PI * 2)));
		c->WarpScale = (0.3f * (float) vid.Width) / (float) vid.Height, 0.3f;
		c->WarpSpeed = (cl.time * 0.025f) - (int) (cl.time * 0.25f);
		c->Gravity = sv_gravity->value * 0.05f;
		c->WarpFactor = 0.005859375f;
		// others to follow...

		// scroll the sky
		c->SkySpeedScale[0] = ((cl.time * 8.0f) - ((int) (cl.time * 8.0f) & ~127)) * 0.0078125f;
		c->SkySpeedScale[1] = ((cl.time * 16.0f) - ((int) (cl.time * 16.0f) & ~127)) * 0.0078125f;

		Vector3Copy (c->EyePositionWorld, ref.ViewOrigin);

		// allow testing based on the value of this cvar
		c->TestFloat = temp1->value;

		c->VidWidth = vid.Width;
		c->VidHeight = vid.Height;

		ref.InverseViewProj.Get (c->InverseViewProj);

		// we need these for shadow volume extrusion
		ref.ViewMatrix.Get (c->ViewMatrix);
		ref.ProjectionMatrix.Get (c->ProjectionMatrix);

		d3d_MainConstants->Unmap ();
	}

	Fog_SetupFrame ();
}


/*
=============
R_Clear
=============
*/
void R_Clear (void)
{
	BOOL ColorClear = FALSE;
	DWORD ClearColor = 0x00000000;

	if (r_realtime_lighting->boolean)
	{
		// always clear to black
		ColorClear = TRUE;
		ClearColor = 0x00000000;
	}
	else if ((ref.ViewLeaf[0] && ref.ViewLeaf[0]->contents == CONTENTS_SOLID) ||
		(ref.ViewLeaf[1] && ref.ViewLeaf[1]->contents == CONTENTS_SOLID) ||
		(ref.ViewLeaf[2] && ref.ViewLeaf[2]->contents == CONTENTS_SOLID))
	{
		ColorClear = TRUE;
		ClearColor = d_8to24table_rgba[2];
	}
	else if (r_lockpvs->value || r_lockfrustum->value)
	{
		ColorClear = TRUE;
		ClearColor = d_8to24table_rgba[108];
	}
	else if (gl_clear->value)
	{
		ColorClear = TRUE;
		ClearColor = d_8to24table_rgba[(int) r_clearcolor->value & 255];
	}

	// and now clear
	if (ColorClear)
	{
		// yes, this is annoying
		float clear[4] = {
			((byte *) &ClearColor)[0] / 255.0f,
			((byte *) &ClearColor)[1] / 255.0f,
			((byte *) &ClearColor)[2] / 255.0f,
			((byte *) &ClearColor)[3] / 255.0f
		};

		d3d_Device->ClearRenderTargetView (ref.RenderTarget->RTV, clear);
	}

	// clear both depth and stencil; we're using stencil wrap so 0 is OK and we don't need to worry about setting stencil ref in our states
	d3d_Device->ClearDepthStencilView (d3d_DepthBuffer.DSV, D3D10_CLEAR_DEPTH | D3D10_CLEAR_STENCIL, 1, 0);
}


void R_PrepareWaterWarp (void)
{
	// choose the correct size to use
	if (R_CheckWaterWarp ())
	{
		vid.ViewWidth = vid.Width;
		vid.ViewHeight = vid.Height;
	}
	else if (scr.DrawMapshot)
	{
		vid.ViewWidth = MAPSHOT_SIZE;
		vid.ViewHeight = MAPSHOT_SIZE;
	}
	else
	{
		vid.ViewWidth = vid.Width;
		vid.ViewHeight = vid.Height - vid.SbarLines;
	}
}


void R_SetupObjectConstants (entity_t *e, float alpha)
{
	// update the per-object cbuffer for this pass
	objectconstants_t *c = NULL;

	if (SUCCEEDED (d3d_ObjectConstants->Map (D3D10_MAP_WRITE_DISCARD, 0, (void **) &c)))
	{
		e->entitymatrix.Get (c->EntityMatrix);
		e->localmatrix.Get (c->LocalMatrix);
		c->lerpBlend = e->poseblend;
		Vector3Copy (c->ShadeLight, e->shadelight);
		Vector3Copy (c->ShadeVector, e->shadevector);
		c->Alpha = alpha;

		d3d_ObjectConstants->Unmap ();
	}
}


void R_SetupCulledEntity (entity_t *e)
{
	// only needed for RT lighting so that we can draw shadows for entitys outside the view frustum
	if (!r_realtime_lighting->boolean) return;

	// check for alpha (fixme - move decode to parse time)
	float entalpha = ENTALPHA_DECODE (e->alpha);

	// alpha ents don't cast shadows
	if (entalpha < 1.0f) return;

	// restore lerping
	e->lerpflags &= ~LERP_RESETALL;

	// load it's matrix
	e->entitymatrix.Load (ref.MVPMatrix);

	// mark it as culled now so that we know to skip it in the main render
	e->culled = TRUE;

	// ultimately we'll add it to a separate list, or mark a "culled" flag in the entity struct; this is just for testing
	if (r_numvisedicts < MAX_EDICTS)
	{
		r_visedicts[r_numvisedicts] = e;
		r_numvisedicts++;
	}
}


void R_SetupEntity (entity_t *e)
{
	// would we be better off just having a single big vertex buffer containing per-object data for all entities
	// and lock it, add each entity's per-object struct, unlock, then draw them as if instanced using the offset
	// into that as a start instance location???  dunno....
	if (!e->model) return;
	if (!r_drawentities->value) return;

	// by default an entity is not culled
	e->culled = FALSE;

	// type-specific setup
	switch (e->model->type)
	{
	case mod_alias:
		if (!R_SetupAliasModel (e))
		{
			R_SetupCulledEntity (e);
			return;
		}
		else break;

	case mod_brush:
		if (!R_SetupBrushModel (e))
		{
			R_SetupCulledEntity (e);
			return;
		}
		else break;

	case mod_sprite:
		R_AddSpriteToAlphaList (e);
		return;

	default:
		return;
	}

	// common setup for all types
	if (r_numvisedicts < MAX_EDICTS)
	{
		// setup world-space position for the entity
		if (e->effects & EF_DEPTHHACK)
		{
			// hack the depth to prevent the viewmodel from poking into walls
			// (this breaks with shadow volumes which require the transform to be the same as the world...)
			e->entitymatrix.Load (1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.3f, 0, 0, 0, 0, 1);
			e->entitymatrix.Mult (ref.MVPMatrix);
		}
		else e->entitymatrix.Load (ref.MVPMatrix);

		//e->entitymatrix.Mult (e->localmatrix);

		// check for alpha (fixme - move decode to parse time)
		float entalpha = ENTALPHA_DECODE (e->alpha);

		// add it to the appropriate list
		if (e == &cl.viewent && (cl.items & IT_RING))
		{
		}
		else if (entalpha < 1.0f)
		{
		}
		else
		{
			// and add it to the active visedicts list
			r_visedicts[r_numvisedicts] = e;
			r_numvisedicts++;
		}
	}
}


/*
================
R_RenderView

ref must be set before the first call
================
*/
void R_RenderView (void)
{
	if (r_norefresh->value)
		return;

	if (!r_worldentity.model || !cl.worldmodel)
		Sys_Error ("R_RenderView: NULL worldmodel");

	// by these are the default render targets we use and are changed if we draw a water warp scene
	// the reason why these exist is so that we know which target to clear
	ref.RenderTarget = &d3d_RenderTarget;

	R_SetupFrame ();

	R_PrepareWaterWarp ();

	R_SetupViewpoint ();
	R_SetupGL ();
	R_Clear ();

	R_MarkLeaves ();	// done here so we know if we're in water

	R_GatherLights ();	// collect together all of our active lights

	R_NewAlphaSurfaces ();	// begin with no alpha surfaces

	// add particle emitters to the draw lists
	R_RunParticleEmitters ();

	// copy off the visedicts so that we can safely add statics at any time
	r_numvisedicts = 0;

	// setup the active visedicts list
	for (int i = 0; i < cl_numvisedicts; i++)
		R_SetupEntity (cl_visedicts[i]);

	// add the viewmodel to the draw lists
	R_DrawViewModel ();

	// clear the extra surfaces list generated by realtime lighting
	R_ClearExtraSurfaces ();

	// switch on our gbuffers for deferred rendering
	R_SwitchToGBuffer ();

	// do standard passes with everything in turn
	R_DrawWorld ();
	R_DrawEntitiesOnList ();

	// add realtime lighting to all entities
	R_DrawRealtimeScene (r_visedicts, r_numvisedicts);

	// and now handle those extra surfaces
	R_HandleExtraSurfaces ();

	// and now draw any alpha objects we have
	R_DrawAlphaObjects ();

	// optionally draw bounding boxes
	if (r_showbboxes->value) R_ShowBBoxes ();

	// and now finish up waterwarping if it was set
	R_FinishWaterWarp ();
}

